from google.appengine.api import users
from google.appengine.api import urlfetch
from time import mktime
from datetime import datetime, timedelta
import feedparser
from models import *
import logging
from application import settings
from google.appengine.api import taskqueue

def verify_update_user():
    # Make sure user always in DB
    user = User.get_or_insert(users.get_current_user().email(),
                              user = users.get_current_user())
    # Update lastest-active date
    user.put()
    return user.key()

def verify_channel_exist(url):
    channel_q = Channel.all(keys_only=True)
    channel_q.filter('link =', url.lower())
    channel_key = channel_q.get()
    return channel_key #Maybe None

def verify_user_subscribed_channel(user_key, channel_key):
    subscribe_q = Subscribe.all()
    subscribe_q.ancestor(user_key)
    subscribe_q.filter('channel =', channel_key)
    if subscribe_q.count(limit=1) == 0:
        return False
    else:
        return True

def verify_feed_url(url):
    feed_fetch = urlfetch.fetch(url.lower(), deadline=settings.URL_FETCH_TIMEOUT)
    if feed_fetch.status_code != 200:
        # Fetch failed
        return False

    rss_parsed = feedparser.parse(feed_fetch.content)
    if rss_parsed == None \
        or 'entries' not in rss_parsed \
        or len(rss_parsed.entries) == 0:
        # Invalid Feed
        return False

    return True

def fetch_channel(channel_key):
    channel = Channel.get(channel_key)

    channel.last_fetch_try = datetime.now()
    feed_fetch = urlfetch.fetch(channel.link, deadline=settings.URL_FETCH_TIMEOUT)
    if feed_fetch.status_code != 200:
        logging.error('feed_fetch.status_code=%d,url=%s', feed_fetch.status_code, channel.link)
        channel.put()
        return
    channel.last_fetch_suc = datetime.now()

    try:
        rss_parsed = feedparser.parse(feed_fetch.content)
    except:
        logging.error('feedparser.parser fail')
        channel.put()
        return

    if 'title' in rss_parsed.feed:
        # Update title
        channel.title = rss_parsed.feed.title
    
    #items_q = channel.item_set
    items_q = Item.all()
    items_q.ancestor(channel_key)
    items_q.order('-pubDate')
    lastest_item = items_q.get()

    # Nested function help for insert new item
    def insert_item_by_entry(entry, have_pubDate):
        new_item = Item(parent=channel_key)
        if 'title' in entry:
            new_item.title = entry.title
        if 'link' in entry:
            new_item.link = entry.link
        if have_pubDate:
            new_item.pubDate = datetime.fromtimestamp(mktime(entry.published_parsed))
        else:
            new_item.pubDate = datetime.now() # Use now as publish time
        new_item.content = ''
        if 'content' in entry:
            for content in entry.content:
                new_item.content += content.value
        elif 'description' in entry:
            new_item.content = entry.description
        new_item.put()

    for entry in rss_parsed.entries:
        if 'published_parsed' in entry:
            # Feed contain publish time
            if lastest_item == None \
                or entry.published_parsed > lastest_item.pubDate.utctimetuple():
                # Compare publish time
                insert_item_by_entry(entry, True)
                channel.last_new_article = datetime.now()
        else:
            # Feed doesn't contain publish time
            if lastest_item == None \
                or entry.title != lastest_item.title:
                # Compare title
                insert_item_by_entry(entry, False)
                channel.last_new_article = datetime.now()
            else:
                # Loop until the latest item.title match entry.title
                break

    channel.put()

def add_channel(url):
    '''
    Add new feed from url for current user
    '''
    url = url.lower()

    user_key = verify_update_user()

    channel_key = verify_channel_exist(url)

    if channel_key == None:
        # Add new feed for entire site
        if verify_feed_url(url):
            channel = Channel.get_or_insert(url.lower(), link=db.Link(url.lower()))
            channel_key = channel.key()

            # Add new feed for this user
            subscribe = Subscribe.get_or_insert(str(user_key)+str(channel_key),
                                                parent=user_key, 
                                                channel=channel_key)
            # Fetch new channel async
            #fetch_channel(channel_key) #Old sync style
            taskqueue.add(url='/q_fetch_channel', params={'channel_key': str(channel_key)})
            return True
        else:
            # Invalid url or feed
            return False
    else:
        if verify_user_subscribed_channel(user_key, channel_key):
            # User already subscribed this feed
            return True
        else:
            # Add new feed for this user
            subscribe = Subscribe.get_or_insert(str(user_key)+str(channel_key),
                                                parent=user_key, 
                                                channel=channel_key)
            return True

def get_channel_by_keystr(user_key, keystr):
    channel_list = []
    name = ''

    if keystr == None:
        name = 'All'
        # Get all subscribed channel
        subscribe_q = Subscribe.all()
        subscribe_q.ancestor(user_key)
        for subscribe in subscribe_q:
            channel_list.append(subscribe.channel.key())
    else:
        tag_channel_key = db.Key(encoded=keystr)
        if tag_channel_key.kind() == Tag.kind():
            name = Tag.get(tag_channel_key).name

            tag_q = Subscribe.all()
            tag_q.ancestor(user_key)
            tag_q.filter('tag =', tag_channel_key)
            for subscribe in tag_q:
                channel_list.append(subscribe.channel.key())
        elif tag_channel_key.kind() == Channel.kind():
            name = Channel.get(tag_channel_key).title

            subscribe_q = Subscribe.all()
            subscribe_q.ancestor(user_key)
            subscribe_q.filter('channel =', tag_channel_key)
            if subscribe_q.count(limit=1) == 0:
                pass
            
            channel_list.append(tag_channel_key)
        else:
            pass

    return channel_list, name

def get_unread_4channel(user_key, channel_key):
    unread_keys = []
    readed_keys = []
    time_now = datetime.now()

    # collect unreaded items for this user and this channel
    unreaded_q = UnreadItem.all()
    unreaded_q.ancestor(user_key)
    unreaded_q.filter('channel =', channel_key)
    for unreaded in unreaded_q.run(limit=settings.MAX_UNREAD_LIMIT):
        unread_keys.append(unreaded.item.key())

    # collect readed items for this user and this channel
    readed_q = ReadedItem.all()
    readed_q.ancestor(user_key)
    readed_q.filter('channel =', channel_key)
    readed_q.filter('read_time >', time_now - timedelta(days=settings.ITEM_WITHIN_DAYS))
    readed_q.order('read_time')
    for readed_item in readed_q.run(limit=settings.READED_PER_CHANNEL):
        readed_keys.append(readed_item.item.key())

    # collect items for this channel
    item_q = Item.all()
    item_q.ancestor(channel_key)
    item_q.filter('pubDate >', time_now - timedelta(days=settings.ITEM_WITHIN_DAYS))
    item_q.order('pubDate')
    for item_key in item_q.run(keys_only=True, limit=settings.ITEM_PER_CHANNEL):
        try:
            # compare and exclude readed items for this user
            i = readed_keys.index(item_key)
            del readed_keys[i]
        except ValueError:
            unread_keys.append(item_key)

    return unread_keys


def gen_maggic_list(user_key, maggic_key, tag_channel_str):
    '''
    Generate unread list for specified user
    Only one maggic is allowed for one user
    '''
    maggic = Maggic.get(maggic_key)

    maggic.items[:] = []
    maggic.maggic_id += 1
    maggic.total = 0
    maggic.offset = 0
    maggic.generate_time = datetime.now()
    maggic.invalid = True
    maggic.put()

    reading_list = []
    name = ''
    channel_list, name = get_channel_by_keystr(user_key, tag_channel_str)
    for channel_key in channel_list:
        reading_list.extend(get_unread_4channel(user_key, channel_key))

    maggic.name = name
    maggic.items = reading_list
    maggic.total = len(reading_list)
    maggic.offset = 0
    maggic.invalid = False
    maggic.put()


class ItemView:
    key = ''
    title = ''
    link = ''
    content=''

def gen_reading_list(maggic_id_str, key_str):
    '''
    Generate reading list.
    If maggic_str is exist, ignore key_str, key_str only used for new maggic
    '''
    user_key = verify_update_user()
    maggic = Maggic.get_or_insert(str(user_key),
                                  parent=user_key,
                                  invalid=True,
                                  maggic_id=0)
    reading_list=[]
    if maggic.invalid \
        or maggic_id_str == None \
        or str(maggic.maggic_id) != maggic_id_str:
        # re-generate maggic list
        gen_maggic_list(user_key, maggic.key(), key_str)
        # re-get maggic list
        maggic = Maggic.all().ancestor(user_key).get()

    for index in range(maggic.offset, maggic.total)[:settings.MAGGIC_STEPLEN]:
        item_key = maggic.items[index]
        item = Item.get(item_key)

        item_view = ItemView()
        item_view.key = str(item_key)
        item_view.title = item.title
        item_view.link = item.link
        item_view.content = item.content

        reading_list.append(item_view)
    
    if maggic.total - maggic.offset > settings.MAGGIC_STEPLEN:
        maggic.offset += settings.MAGGIC_STEPLEN
    else:
        maggic.offset = maggic.total
        maggic.invalid = True

    maggic.put()
    return {'maggic_str':str(maggic.maggic_id), 
            'list':reading_list,
            'folder':maggic.name}

def mark_item_readed(user_key, item_key):
    item = Item.get(item_key)

    # Invalid item_key
    if item == None:
        return

    subscribe_q = Subscribe.all()
    subscribe_q.ancestor(user_key)
    subscribe_q.filter('channel =', item.parent())
    # Invalid subscribe
    if subscribe_q.count(limit=1) == 0:
        return

    readed_item = ReadedItem.get_or_insert(str(user_key)+str(item_key),
                                           parent=user_key,
                                           channel=item.parent(),
                                           item=item_key)


def gen_subscribe_list():
    user_key = verify_update_user()

    subscribe_list = []
    subscribe_q = Subscribe.all()
    subscribe_q.ancestor(user_key)
    for subscribe in subscribe_q:
        subscribe_list.append(subscribe.channel.title)

    return subscribe_list